﻿using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using BssDs.Client.UI.Screens.Core.Windows;

namespace BssDs.Client.UI.Controls.ExtendedControl.Tab
{
	/// <summary>
	/// マウスドラッグでTabItemの並び替えができるTabControl。
	/// <remarks>
	/// ItemsSourceがObservableCollectionの場合のみItemsSourceを並び替える。
	/// それ以外のときはItemsを並び替える。
	/// </remarks>
	/// </summary>
	public class TabControlEx : TabControl
	{
		// 新規追加タブの位置
		public enum NewTabItemAddPositions
		{
			Head,
			TailEnd
		}


		/// <summary>
		/// ルーティングイベントの登録
		/// </summary>
		public static readonly RoutedEvent NewTabItemAddedEvent =
			EventManager.RegisterRoutedEvent("NewTabItemAdded", RoutingStrategy.Bubble,
			typeof(RoutedEventHandler), typeof(TabItemEx));

		// AddedNewTabイベント
		public event RoutedEventHandler NewTabItemAdded
		{
			add
			{
				AddHandler(NewTabItemAddedEvent, value);
			}
			remove
			{
				RemoveHandler(NewTabItemAddedEvent, value);
			}
		}

		
		// マウスドラッグでTabItemを移動しているときのindexに使用。
		// マウスカーソルがTabItemを指していないときはこの値になる。
		private const int INVALID_MOVE_TO_INDEX = int.MinValue;

		// PointToScreenなどで座標に誤差が生じる。その誤差含みの座標で判定するときの閾値。
		private const double ROUNDING_ERROR_THRESHOLD = 0.001;

		private const double DEFAULT_DRAGGED_ITEM_TO_MOVE_OPACITY = 0.5;

		#region Property definition

		public static readonly DependencyProperty DraggingCursorAdornerOpacityMaskProperty = DependencyProperty.Register(
				"DraggingCursorAdornerOpacityMask",
				typeof(Brush),
				typeof(TabControlEx),
				new FrameworkPropertyMetadata(
						null,
						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
				)
		);

		/// <summary>
		/// TabItem移動中に表示される「ドラッグ中のTabItemヘッダーのコピー」のマスク。
		/// アルファ値のみ使用。RGBは無視。
		/// ドラッグ中に変更しても即座に反映されない。
		/// (未確認だが、タブを切り替えてからドラッグ開始するまでに変更したら反映されるはず。)
		/// </summary>
		public Brush DraggingCursorAdornerOpacityMask
		{
			get { return (Brush)GetValue(DraggingCursorAdornerOpacityMaskProperty); }
			set { SetValue(DraggingCursorAdornerOpacityMaskProperty, value); }
		}

		public static readonly DependencyProperty DraggingCursorAdornerOffsetProperty = DependencyProperty.Register(
				"DraggingCursorAdornerOffset",
				typeof(Point),
				typeof(TabControlEx),
				new FrameworkPropertyMetadata(
						new Point(double.NaN, double.NaN),
						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
				)
		);

		/// <summary>
		/// TabItem移動中に表示される「ドラッグ中のTabItemヘッダーのコピー」のマウスカーソルからの相対位置。
		/// ドラッグ中に変更しても即座に反映されない。
		/// オフセットの初期値は(NaN,NaN)。
		/// offset.XがNaNのときは0が、offset.YがNaNのときは(TabItemのドラッグ開始時の実際の高さ÷2)が使用される。
		/// </summary>
		public Point DraggingCursorAdornerOffset
		{
			get { return (Point)GetValue(DraggingCursorAdornerOffsetProperty); }
			set { SetValue(DraggingCursorAdornerOffsetProperty, value); }
		}

		public static readonly DependencyProperty MoveCursorCanvasProperty = DependencyProperty.Register(
				"MoveCursorCanvas",
				typeof(Canvas),
				typeof(TabControlEx),
				new FrameworkPropertyMetadata(
						null,
						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
						OnMoveCursorCanvasChanged
				)
		);

		/// <summary>
		/// TabItemの移動先(どのタブとどのタブの間に入るか)を指すカーソル。
		/// ホットスポットは(0,0)になるように調節すること。
		/// ドラッグ中に変更しても即座に反映されない。
		/// </summary>
		public Canvas MoveCursorCanvas
		{
			get { return (Canvas)GetValue(MoveCursorCanvasProperty); }
			set { SetValue(MoveCursorCanvasProperty, value); }
		}

		private static void OnMoveCursorCanvasChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
		{
			TabControlEx thisCtrl = (TabControlEx)obj;
			Debug.Assert(thisCtrl._moveCursor != null);

			thisCtrl._moveCursor.CursorCanvas = (Canvas)args.NewValue;
		}

		public static readonly DependencyProperty MoveCursorHitTestCorrectionProperty = DependencyProperty.Register(
				"MoveCursorHitTestCorrection",
				typeof(double),
				typeof(TabControlEx),
				new FrameworkPropertyMetadata(
						(double)1.0,
						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
				)
		);

		/// <summary>
		/// マウスカーソルがTabItem上にあるか判定するときのItemsHostサイズの補正値。
		/// デフォルトのレイアウトだとItemsHost(TabPanel)のマージンが-1.0されており、正常に判定できない。
		/// そこで、MoveCursorHitTestCorrectionの分だけTabItemを縮めて判定する。
		/// Templateを指定する場合は0にしてもよい。
		/// </summary>
		public double MoveCursorHitTestCorrection
		{
			get { return (double)GetValue(MoveCursorHitTestCorrectionProperty); }
			set { SetValue(MoveCursorHitTestCorrectionProperty, value); }
		}

		public static readonly DependencyProperty IsDraggingItemToMoveProperty = DependencyProperty.Register(
				"IsDraggingItemToMove",
				typeof(bool),
				typeof(TabControlEx),
				new FrameworkPropertyMetadata(
						false,
						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
				)
		);

		/// <summary>
		/// TabItemをドラッグで移動中かどうか。trueなら移動中。
		/// </summary>
		public bool IsDraggingItemToMove
		{
			get { return (bool)GetValue(IsDraggingItemToMoveProperty); }
			private set { SetValue(IsDraggingItemToMoveProperty, value); }
		}

		public void SetIsItemDraggingToMove(TabItem item, bool value)
		{
			item.SetValue(TabItemIsDraggingToMove.ValueProperty, value);
		}

		public bool GetIsItemDraggingToMove(TabItem item)
		{
			return (bool)item.GetValue(TabItemIsDraggingToMove.ValueProperty);
		}

		public static readonly DependencyProperty DraggedItemToMoveProperty = DependencyProperty.Register(
				"DraggedItemToMove",
				typeof(TabItem),
				typeof(TabControlEx),
				new FrameworkPropertyMetadata(
						null,
						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
				)
		);

		/// <summary>
		/// ドラッグで移動中のTabItem。
		/// </summary>
		public TabItem DraggedItemToMove
		{
			get { return (TabItem)GetValue(DraggedItemToMoveProperty); }
			private set { SetValue(DraggedItemToMoveProperty, value); }
		}

		public static readonly DependencyProperty HeaderElementNameProperty = DependencyProperty.Register(
				"HeaderElementName",
				typeof(string),
				typeof(TabControlEx),
				new FrameworkPropertyMetadata(
						null,
						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
						OnHeaderElementNameChanged
				)
		);

		/// <summary>
		/// Templateを使った場合、Templateの名前スコープ内でItemsHostを載せるコントロールの名前を指定。
		/// TabItemのドラッグ移動範囲のチェックに使用される。
		/// Templateで、レイアウトの都合上ItemsHostの一部が隠れてしまう場合や、
		/// ItemsHostをScrollViewerに載せる場合などを想定。
		/// このプロパティを指定する場合は、TemplateのTargetTypeをこのTabControlにすること。
		/// 
		/// このプロパティに登録されたコントロールの枠より外にあるTabItemにはドラッグできない。
		/// ドラッグ先の判定はTabItemの四隅のうつ2つ(TabStripPlacement.Topのときは下の左右など)の座標を使う。
		/// レイアウトでその隅が隠れないように注意。
		/// TabItemの四隅はちょっとしたマージンのとり方1つで隠れてしまうことがあるので、要調整。
		/// 
		/// 初期値はnullで、その場合thisのサイズでチェックされる。
		/// </summary>
		public string HeaderElementName
		{
			get { return (string)GetValue(HeaderElementNameProperty); }
			set { SetValue(HeaderElementNameProperty, value); }
		}

		private static void OnHeaderElementNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
		{
			TabControlEx thisCtrl = (TabControlEx)obj;
			string newName = args.NewValue as string;

			if (newName == null)
				return;

			if (thisCtrl.IsLoaded)
				thisCtrl.UpdateHeaderElement(newName);
		}

		/// <summary>
		/// 初期表示の際、TabItemが無かった場合に空ページのTabItemを表示させるかどうか。
		/// </summary>
		private bool _IsShowStartupBlankPage = true;
		public bool IsShowStartupBlankPage
		{
			get { return _IsShowStartupBlankPage; }
			set { _IsShowStartupBlankPage = value; }
		}

		/// <summary>
		/// 新規追加タブのインスタンス
		/// </summary>
		private NewAddTabItem _NewTabItemAddInstance;
		private NewAddTabItem NewTabItemAddInstance
		{
			get { return _NewTabItemAddInstance; }
			set { _NewTabItemAddInstance = value; }
		}

		/// <summary>
		/// 新規追加タブの表示位置
		/// </summary>
		private NewTabItemAddPositions _NewTabItemAddPosition = NewTabItemAddPositions.TailEnd;
		public NewTabItemAddPositions NewTabItemAddPosition
		{
			get { return _NewTabItemAddPosition; }
			set { _NewTabItemAddPosition = value; }
		}

		private int _LastSelectedIndex = INVALID_MOVE_TO_INDEX;
		public int LastSelectedIndex
		{
			get { return _LastSelectedIndex; }
			set { _LastSelectedIndex = value; }
		}
		#endregion // Property definition

		private TabMoveCursor _moveCursor;
		private TabDraggingCursorAdorner _draggingCursorAdorner;
		private Point _pointDragStart;
		private TabItem _dragOverItem;
		private int _moveToIndex;
		private FrameworkElement _headerElement;

		public TabControlEx()
		{
			_moveCursor = new TabMoveCursor(this);
			_draggingCursorAdorner = new TabDraggingCursorAdorner(this);
			ResetDragParameter();

			Loaded += OnLoaded;
			this.AddHandler(NewAddTabItem.NewTabItemAddButtonClickEvent, new RoutedEventHandler(this.NewTabItemAddButton_Click));
			this.AddHandler(TabItemEx.TabItemClosingEvent, new RoutedEventHandler(this.TabItem_Closing));

			// ドラッグ移動中のTabItemヘッダーのデフォルトスタイルを設定
			Style style = new Style();
			Trigger trigger = new Trigger();
			trigger.Property = TabItemIsDraggingToMove.ValueProperty;
			trigger.Value = true;
			Setter setter = new Setter(TabItem.OpacityProperty, DEFAULT_DRAGGED_ITEM_TO_MOVE_OPACITY);
			trigger.Setters.Add(setter);
			style.Triggers.Add(trigger);
			ItemContainerStyle = style;

			this.IsTabStop = true;
		}

		public void NewTabItemAddButton_Click(object source, RoutedEventArgs args)
		{
			this.AddTabItemEx();
		}

		public void TabItem_Closing(object source, RoutedEventArgs args)
		{
			TabItem tabItem = (TabItem)args.Source;

			if (tabItem != null)
			{
				TabControlEx tabCtrl = (TabControlEx)tabItem.Parent;
				if (tabCtrl != null)
				{
					tabCtrl.Items.Remove(tabItem);

					if (this.IsShowStartupBlankPage && Items.Count == 1)
						if (Items[0] is NewAddTabItem)
							this.AddTabItemEx();
				}
			}
		}
	
		private void OnLoaded(object sender, RoutedEventArgs evt)
		{
			AdornerLayer layer = AdornerLayer.GetAdornerLayer(this);
			Debug.Assert(layer != null);
			layer.Add(_moveCursor);
			layer.Add(_draggingCursorAdorner);

			SettingExtendedTabControl();

			UpdateHeaderElement(HeaderElementName);
		}

		protected override void OnSelectionChanged(SelectionChangedEventArgs e)
		{
			base.OnSelectionChanged(e);

			if (this.Equals(e.OriginalSource))
			{
				if (LastSelectedIndex >= 0)
				{
					TabItemEx lastTabItemEx = this.Items[LastSelectedIndex] as TabItemEx;
					if (lastTabItemEx != null)
					{
						lastTabItemEx.SetTabItemImage(TabItemEx.TabImages.UnSelected);
					}
				}

				if (this.SelectedIndex >= 0 && !IsDraggingItemToMove)
				{
					TabItemEx currentTabItemEx = this.Items[this.SelectedIndex] as TabItemEx;
					if (currentTabItemEx != null)
					{
						currentTabItemEx.SetTabItemImage(TabItemEx.TabImages.Selected);
					}
				}

				this.LastSelectedIndex = this.SelectedIndex;
			}


		}

		protected override void OnTemplateChanged(ControlTemplate oldTemplate, ControlTemplate newTemplate)
		{
			base.OnTemplateChanged(oldTemplate, newTemplate);

			if (IsLoaded)
				UpdateHeaderElement(HeaderElementName);
		}

		protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
		{
			base.PrepareContainerForItemOverride(element, item);

			TabItem tabItem = (TabItem)element;
			tabItem.AllowDrop = true;
			tabItem.PreviewMouseLeftButtonDown += OnTabItemPreviewMouseLeftButtonDown;
			tabItem.MouseLeave += OnTabItemMouseLeave;
			tabItem.PreviewMouseMove += OnTabItemPreviewMouseMove;
			tabItem.PreviewMouseLeftButtonUp += OnTabItemPreviewMouseLeftButtonUp;
			tabItem.PreviewDragEnter += OnTabItemPreviewDragEnter;
			tabItem.PreviewDragOver += OnTabItemPreviewDragOver;
			tabItem.PreviewDragLeave += OnTabItemPreviewDragLeave;
			tabItem.PreviewDrop += OnTabItemPreviewDrop;
			tabItem.GiveFeedback += OnTabItemDragGiveFeedback;
		}

		protected override void ClearContainerForItemOverride(DependencyObject element, object item)
		{
			TabItem tabItem = (TabItem)element;
			tabItem.PreviewMouseLeftButtonDown -= OnTabItemPreviewMouseLeftButtonDown;
			tabItem.MouseLeave -= OnTabItemMouseLeave;
			tabItem.PreviewMouseMove -= OnTabItemPreviewMouseMove;
			tabItem.PreviewMouseLeftButtonUp -= OnTabItemPreviewMouseLeftButtonUp;
			tabItem.PreviewDragEnter -= OnTabItemPreviewDragEnter;
			tabItem.PreviewDragOver -= OnTabItemPreviewDragOver;
			tabItem.PreviewDragLeave -= OnTabItemPreviewDragLeave;
			tabItem.PreviewDrop -= OnTabItemPreviewDrop;
			tabItem.GiveFeedback -= OnTabItemDragGiveFeedback;

			base.ClearContainerForItemOverride(element, item);
		}

		protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs evt)
		{
			base.OnPropertyChanged(evt);

			if (evt.Property == TabControlEx.TabStripPlacementProperty)
			{
				switch (this.TabStripPlacement)
				{
					case Dock.Top:
						_moveCursor.Rotate(0);
						break;
					case Dock.Left:
						_moveCursor.Rotate(-90);
						break;
					case Dock.Right:
						_moveCursor.Rotate(90);
						break;
					case Dock.Bottom:
						_moveCursor.Rotate(180);
						break;
				}
			}
		}

		private void OnTabItemPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs evt)
		{
			_pointDragStart = evt.GetPosition((TabItem)sender);
		}

		private void OnTabItemMouseLeave(object sender, MouseEventArgs evt)
		{
			if (evt.LeftButton == MouseButtonState.Pressed && (!IsDraggingItemToMove))
				ResetDragParameter();
		}

		private void OnTabItemPreviewMouseMove(object sender, MouseEventArgs evt)
		{
			if (evt.LeftButton == MouseButtonState.Released || IsDraggingItemToMove)
				return;

			TabItem item = sender as TabItem;
			if (CheckDragDistance(evt.GetPosition(item)))
			{
				PrepareDrag(item);
				DragDrop.DoDragDrop(item, item, DragDropEffects.Move);
				ResetDragParameter();
			}
		}

		private void OnTabItemPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs evt)
		{
			ResetDragParameter();
		}

		private void OnTabItemPreviewDragEnter(object sender, DragEventArgs evt)
		{
			if (sender as NewAddTabItem == null)
			{

				CursorMoveTo(sender as TabItem, evt);
			}
		}

		private void OnTabItemPreviewDragOver(object sender, DragEventArgs evt)
		{
			CursorMoveTo(sender as TabItem, evt);
		}

		private void OnTabItemPreviewDragLeave(object sender, DragEventArgs evt)
		{
			HideCursor();
		}

		private void OnTabItemPreviewDrop(object sender, DragEventArgs evt)
		{

			if (_moveToIndex != INVALID_MOVE_TO_INDEX)
				MoveItem();
		}

		void OnTabItemDragGiveFeedback(object sender, GiveFeedbackEventArgs evt)
		{
			_draggingCursorAdorner.Move(Win32dll.GetCursorPos(this));
			evt.UseDefaultCursors = false;
			evt.Handled = true;
		}

		private void PrepareDrag(TabItem item)
		{
			if (item as NewAddTabItem == null)
			{
				if (this.NewTabItemAddInstance.IsEnabled)
					this.NewTabItemAddInstance.IsEnabled = false;

				DraggedItemToMove = item;
				IsDraggingItemToMove = true;
				SetIsItemDraggingToMove(item, true);
				_draggingCursorAdorner.Show(item, Win32dll.GetCursorPos(this));
			}
		}

		private void ResetDragParameter()
		{
			HideCursor();

			if (DraggedItemToMove != null)
				SetIsItemDraggingToMove(DraggedItemToMove, false);

			_draggingCursorAdorner.Hide();
			_pointDragStart = new Point(double.NaN, double.NaN);
			IsDraggingItemToMove = false;
			DraggedItemToMove = null;
			_moveToIndex = INVALID_MOVE_TO_INDEX;

			if (this.NewTabItemAddInstance != null)
				this.NewTabItemAddInstance.IsEnabled = true;
		}

		private bool CheckDragDistance(Point pointNow)
		{
			if (double.IsNaN(_pointDragStart.X)) // MouseDown後ドラッグ判定前に他のコントロールに移るとNaNが入っている
				return false;

			double dx = Math.Abs(pointNow.X - _pointDragStart.X);
			double dy = Math.Abs(pointNow.Y - _pointDragStart.Y);
			double thresholdX = SystemParameters.MinimumHorizontalDragDistance;
			double thresholdY = SystemParameters.MinimumVerticalDragDistance;

			return thresholdX < dx || thresholdY < dy;
		}

		private void CursorMoveTo(TabItem item, DragEventArgs evt)
		{
			if (DraggedItemToMove != null)
			{
				int dragged = ItemContainerGenerator.IndexFromContainer(DraggedItemToMove);
				int pointing = ItemContainerGenerator.IndexFromContainer(item);

				bool pointToHead = IsMouseCursorPointingToHead(item, evt.GetPosition(item));
				bool isVisible = IsMoveCursorVisible(item, dragged, pointing, pointToHead);

				_moveToIndex = INVALID_MOVE_TO_INDEX;
				if (isVisible)
					_moveToIndex = (pointToHead ? pointing : pointing + 1);

				_moveCursor.MoveTo(item, pointToHead, isVisible);

				_dragOverItem = item;
			}
		}

		private void HideCursor()
		{
			_moveCursor.Hide();
			_dragOverItem = null;
			_moveToIndex = INVALID_MOVE_TO_INDEX;
		}

		private void MoveItem()
		{
			if (DraggedItemToMove != null)
			{
				int dragged = ItemContainerGenerator.IndexFromContainer(DraggedItemToMove);
				int insert = _moveToIndex;
				if (dragged < insert)
					insert--;

				if (IsItemsSourceObservableCollection())
				{
					Type ItemsSourceType = ItemsSource.GetType();
					MethodInfo methodInfo = ItemsSourceType.GetMethod("Move");
					methodInfo.Invoke(ItemsSource, new object[] { dragged, insert });
				}
				else
				{
					var item = Items.GetItemAt(dragged);
					Items.RemoveAt(dragged);
					Items.Insert(insert, item);

					SelectedIndex = insert;
				}
			}
		}

		private bool IsItemsSourceObservableCollection()
		{
			if (ItemsSource == null)
				return false;
			Type itemsSourceType = ItemsSource.GetType();
			if (!itemsSourceType.IsGenericType)
				return false;

			Type itemsSourceTypeDefinition = itemsSourceType.GetGenericTypeDefinition();
			return itemsSourceTypeDefinition == typeof(ObservableCollection<>);
		}

		// マウスカーソルがTabItemの先頭側を指しているか末尾側を指しているか?
		// mousePositionは_dragOverItem上のマウスカーソル位置。
		private bool IsMouseCursorPointingToHead(TabItem item, Point mousePosition)
		{
			Debug.Assert(item != null);

			Dock placement = this.TabStripPlacement;
			if (placement == Dock.Top || placement == Dock.Bottom)
				return mousePosition.X < item.ActualWidth / 2.0;
			else
				return mousePosition.Y < item.ActualHeight / 2.0;
		}

		private bool IsMoveCursorVisible(TabItem item, int dragged, int pointing, bool pointToHead)
		{
			// MoveCursorがHeaderFrameElementおよびItemsHostのパネルからはみ出す場合はfalseを返す。
			// ドラッグ中はマウスカーソルのTabItemの出入りで可視/不可視が切り替わっているので、
			// この処理が必要になるのはレイアウトの都合でTabItemが一部隠れるときのみ。
			FrameworkElement headerElement = _headerElement;
			if (headerElement == null)
				headerElement = this;
			Panel hostPanel = FindTabItemsHostPanelFromAncestor(item);
			Dock placement = this.TabStripPlacement;
			Point cornerPoint = new Point();
			double curX, curY;
			double moveCursorHitTestThreshold = MoveCursorHitTestCorrection;

			if (placement == Dock.Top || placement == Dock.Bottom)
			{
				cornerPoint.X = (pointToHead ? 0 : item.ActualWidth);
				cornerPoint.Y = (placement == Dock.Top ? item.ActualHeight - moveCursorHitTestThreshold : moveCursorHitTestThreshold);
				Point screenPoint = item.PointToScreen(cornerPoint);

				Func<FrameworkElement, Point, bool> contains =
						(FrameworkElement elem, Point p) =>
						{
							p = elem.PointFromScreen(p);
							curX = (p.X == 0 ? 0 : p.X - ROUNDING_ERROR_THRESHOLD);
							curY = (p.Y == 0 ? 0 : p.Y - ROUNDING_ERROR_THRESHOLD);

							if (curY < 0 || elem.ActualHeight < curY)
							{
								return false;
							}
							if (pointToHead && curX < 0)
							{
								return false;
							}
							if ((!pointToHead) && (elem.ActualWidth < curX))
							{
								return false;
							}
							return true;
						};

				if ((!contains(headerElement, screenPoint)) || (!contains(hostPanel, screenPoint)))
				{
					return false;
				}
			}
			else if (placement == Dock.Left || placement == Dock.Right)
			{
				cornerPoint.X = (placement == Dock.Left ? item.ActualWidth - moveCursorHitTestThreshold : moveCursorHitTestThreshold);
				cornerPoint.Y = (pointToHead ? 0 : item.ActualHeight);
				Point screenPoint = item.PointToScreen(cornerPoint);

				Func<FrameworkElement, Point, bool> contains =
						(FrameworkElement elem, Point p) =>
						{
							p = elem.PointFromScreen(p);
							curX = (p.X == 0 ? 0 : p.X - ROUNDING_ERROR_THRESHOLD);
							curY = (p.Y == 0 ? 0 : p.Y - ROUNDING_ERROR_THRESHOLD);
							if (curX < 0 || elem.ActualWidth < curX)
							{
								return false;
							}
							if (pointToHead && curY < 0)
							{
								return false;
							}
							if ((!pointToHead) && (elem.ActualHeight < curY))
							{
								return false;
							}
							return true;
						};

				if ((!contains(headerElement, screenPoint)) || (!contains(hostPanel, screenPoint)))
				{
					return false;
				}
			}

			// タブ位置の入れ替わりが無いときはfalseを返す
			if (dragged == pointing - 1)
				return !pointToHead;
			else if (dragged == pointing + 1)
				return pointToHead;
			else if (dragged != pointing)
				return true;

			return false;
		}

		private void UpdateHeaderElement(string elemName)
		{
			_headerElement = null;

			if (elemName == null)
				return;

			if (Template == null)
				throw new InvalidOperationException("HeaderElementNameはTemplate使用時にのみ指定できます。");

			Type targetType = Template.TargetType;
			Type thisType = typeof(TabControlEx);
			if (targetType != thisType && (!targetType.IsSubclassOf(thisType)))
				throw new InvalidOperationException("HeaderElementName指定時はTemplateのTargetTypeは" + thisType.FullName + "でなければなりません。");

			_headerElement = this.Template.FindName(elemName, this) as FrameworkElement;
			if (_headerElement == null)
				throw new ArgumentException("指定された名前の要素が見つかりません。");
			if (!this.IsAncestorOf(_headerElement))
				throw new ArgumentException("指定する要素は" + thisType.FullName + "のTemplateに含まれていなければなりません。");
			if (FindTabItemsHostPanelFromDescendant(_headerElement) == null)
				throw new ArgumentException("指定する要素はTabItemまたはTabItemExのItemsHostを持たなければなりません。");
		}

		private Panel FindTabItemsHostPanelFromAncestor(DependencyObject obj)
		{
			while (obj != null)
			{
				Panel panel = obj as Panel;
				if (panel != null && panel.IsItemsHost)
				{
					return panel;
				}
				obj = VisualTreeHelper.GetParent(obj);
			}

			return null;
		}

		private Panel FindTabItemsHostPanelFromDescendant(DependencyObject obj)
		{
			for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
			{
				DependencyObject child = VisualTreeHelper.GetChild(obj, i);
				Panel panel = child as Panel;
				if (panel != null && panel.IsItemsHost)
				{
					return panel;
				}
				DependencyObject descendant = FindTabItemsHostPanelFromDescendant(child);
				if (descendant != null)
				{
					return (Panel)descendant;
				}
			}

			return null;
		}

		private void SettingExtendedTabControl()
		{
			int insert;

			if (Items.Count < 0)
			{
				insert = 0;
			}
			else
			{
				switch (this.NewTabItemAddPosition)
				{
					case NewTabItemAddPositions.Head:
						insert = 0;
						break;
					case NewTabItemAddPositions.TailEnd:
						insert = Items.Count;
						break;
					default:
						throw new InvalidOperationException("新規追加用のタブを表示する位置は Head または TailEnd のいずれかを指定してください。");
				}
			}

			this.NewTabItemAddInstance = new NewAddTabItem();
			Items.Insert(insert, this.NewTabItemAddInstance);

			if (this.IsShowStartupBlankPage && Items.Count == 1)
				if (Items[0] is NewAddTabItem)
					this.AddTabItemEx();
		}

		private void AddTabItemEx()
		{
			int insert;

			if (Items.Count < 0)
			{
				insert = 0;
			}
			else
			{
				switch (this.NewTabItemAddPosition)
				{
					case NewTabItemAddPositions.Head:
						insert = Items.Count;
						break;
					case NewTabItemAddPositions.TailEnd:
						insert = Items.Count - 1;
						break;
					default:
						throw new InvalidOperationException("新規追加用のタブを表示する位置は Head または TailEnd のいずれかを指定してください。");
				}
			}

			Items.Insert(insert, new TabItemEx());
			SelectedIndex = insert;

			// NewTabItemAddedEventイベントを発生させる
			RaiseEvent(new RoutedEventArgs(NewTabItemAddedEvent));
		}

		public int GetNewTabItemAddedPosition()
		{
			int index;

			switch (this.NewTabItemAddPosition)
			{
				case TabControlEx.NewTabItemAddPositions.Head:
					index = this.Items.Count - 1;
					break;
				case NewTabItemAddPositions.TailEnd:
					index = this.Items.Count - 2;
					break;
				default:
					throw new InvalidOperationException("新規追加用のタブを表示する位置は Head または TailEnd のいずれかを指定してください。");
			}

			return index;
		}
	}
}
